// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

#include "coin2/exchange/base/api_base/grpc/grpc_context.h"
#include "coin2/exchange/base/executor/topic_publisher.h"
#include "coin2/exchange/base/order/external.h"
#include "coin2/exchange/base/order/message_bus.h"
#include "coin2/exchange/base/order/order_context_manager.h"
#include "coin2/exchange/base/order/product_info_cache.h"
#include "coin2/exchange/base/order/symbol_cache.h"
#include "quickfix/Message.h"

namespace coin2::exchange::base::order {

class BaseParser {
 public:
  using JsonDocument = rapidjson::Document;
  using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;
  using SubmitOrderResponse = coin::proto::SubmitOrderResponse;
  using MultiSubmitResponse = coin::proto::MultiSubmitResponse;
  using CancelOrderResponse = coin::proto::CancelOrderResponse;
  using MultiCancelResponse = coin::proto::MultiCancelResponse;
  using ConversionElement = coin::proto::ConversionElement;
  using ConversionBundle = coin::proto::ConversionBundle;
  using ProductOrderBundle = coin::proto::ProductOrderBundle;
  using ProductFillBundle = coin::proto::ProductFillBundle;
  using OrderEvent = coin::proto::OrderEvent;
  using AccountBalance = coin::proto::AccountBalance;
  using ProductOrderElement = coin::proto::ProductOrderElement;
  using ProductFillElement = coin::proto::ProductFillElement;
  using NftTokenInfo = ::coin::proto::NftTokenInfo;
  using AccountPosition = coin::proto::AccountPosition;
  using OrderErrorCode = coin::proto::OrderErrorCode;
  using OrderSubTopicId = base::order::OrderSubTopicId;
  using RawMessageProto = coin::proto::RawMessageProto;
  using CurrencyBalance = coin::proto::CurrencyBalance;
  using CurrencyTransferBundle = coin::proto::CurrencyTransferBundle;
  using FundingFeeBundle = coin::proto::FundingFeeBundle;
  using RawBalanceMap = std::unordered_map<std::string, CurrencyBalance>;
  using ProductPosition = coin::proto::ProductPosition;
  using RawPositionMap = std::unordered_map<std::string, ProductPosition>;
  using GrpcContext = coin2::exchange::base::api_base::grpc::GrpcContext;

  enum MsgType {
    IGNORABLE = 0,
    ACCOUNT_BALANCE = 1,
    ACCOUNT_POSITION = 2,
    ORDER_FILL = 4,
    ORDER_STATUS = 8,
    SUBMIT_RESPONSE = 16,
    MULTI_SUBMIT_RESPONSE = 512,
    CANCEL_RESPONSE = 32,
    MULTI_CANCEL_RESPONSE = 64,
    TRANSFER = 128,
    CONVERSION = 256,
    // web3 specific
    NFT_TOKEN_INFO = 512,
    FUNDING_FEE = 1024,
  };

  struct RawMessage {
    int type{0};
    int64_t timestamp{0};
    mutable int64_t raw_msg_id{0};
    mutable MarketExchangeApi mea;

    OrderExecutionProto order_execution;
    coin::proto::ErrorCode error_code;

    mutable OrderContext* oc{nullptr};
    std::vector<const OrderContext*> oc_list;
    std::shared_ptr<AsioHttpContext> http_context;

    std::string* data{nullptr};
    const OrderSpec* order_spec{nullptr};
    JsonValue* json{nullptr};
    FIX::Message* fix_message{nullptr};
    std::string_view raw_buffer;
    std::shared_ptr<GrpcContext> grpc_context;
    const google::protobuf::Message* proto_msg{nullptr};

    // tmp storage area, for replay, will refactor later.
    std::unique_ptr<OrderSpec> order_spec_tmp;
    std::string data_tmp;
    std::vector<std::unique_ptr<OrderContext>> oc_list_tmp;
    std::shared_ptr<google::protobuf::Message> proto_msg_tmp;
    std::unique_ptr<FIX::Message> fix_message_ptr = {};  // for replay only

    std::string DebugString() const;
    std::string SerializeToString() const;
    RawMessageProto ToProto() const;
    static RawMessage FromProto(const RawMessageProto& proto);
  };

  class ParseResult {
   public:
    int64_t timestamp() const { return timestamp_; }
    bool has_error() const { return error_code_ != OrderErrorCode::ORDER_NO_ERROR; }
    const auto& fill_list() const { return fill_bundle_; }
    const auto& order_list() const { return order_bundle_; }
    const auto& conversion_list() const { return conversion_bundle_; }
    const auto& account_balance() const { return balance_; }
    const auto& account_position() const { return position_; }
    const auto& currency_transfer_bundle() const { return currency_transfer_bundle_; }
    const auto& funding_fee_bundle() const { return funding_fee_bundle_; }
    const auto& submit_response() const { return submit_response_; }
    const auto& multi_submit_response() const { return multi_submit_response_; }
    const auto& cancel_response() const { return cancel_response_; }
    const auto& multi_cancel_response() const { return multi_cancel_response_; }
    const auto& nft_token_info() const { return nft_token_info_; }

    bool is_account_balance() const { return (type_bits_ & MsgType::ACCOUNT_BALANCE) > 0; }
    bool is_account_position() const { return (type_bits_ & MsgType::ACCOUNT_POSITION) > 0; }
    bool is_fill() const { return (type_bits_ & MsgType::ORDER_FILL) > 0; }
    bool is_order() const { return (type_bits_ & MsgType::ORDER_STATUS) > 0; }
    bool is_conversion() const { return (type_bits_ & MsgType::CONVERSION) > 0; }
    bool is_submit_response() const { return (type_bits_ & MsgType::SUBMIT_RESPONSE) > 0; }
    bool is_multi_submit_response() const {
      return (type_bits_ & MsgType::MULTI_SUBMIT_RESPONSE) > 0;
    }
    bool is_cancel_response() const { return (type_bits_ & MsgType::CANCEL_RESPONSE) > 0; }
    bool is_transfer() const { return (type_bits_ & MsgType::TRANSFER) > 0; }
    bool is_funding_fee() const { return (type_bits_ & MsgType::FUNDING_FEE) > 0; }
    bool is_multi_cancel_response() const {
      return (type_bits_ & MsgType::MULTI_CANCEL_RESPONSE) > 0;
    }
    bool is_nft_token_info() const { return (type_bits_ & MsgType::NFT_TOKEN_INFO) > 0; }

    void set_error_code(OrderErrorCode code) {
      error_code_ = code;
      order_event_[num_of_events_].set_error_code(code);
    }

    void set_type(MsgType type) { type_bits_ |= type; }

    void Clear() {
      error_code_ = OrderErrorCode::ORDER_NO_ERROR;
      num_of_events_ = 0;
      type_bits_ = 0;
    }

    // Only used by parser.
    auto* mutable_order_event() { return &order_event_[num_of_events_]; }
    auto* mutable_submit_response() { return &submit_response_; }
    auto* mutable_multi_submit_response() { return &multi_submit_response_; }
    auto* mutable_cancel_response() { return &cancel_response_; }
    auto* mutable_multi_cancel_response() { return &multi_cancel_response_; }
    auto* mutable_conversion_list() { return &conversion_bundle_; }
    auto* mutable_order_list() { return &order_bundle_; }
    auto* mutable_fill_list() { return &fill_bundle_; }
    auto* mutable_account_balance() { return &balance_; }
    auto* mutable_account_position() { return &position_; }
    auto* mutable_currency_transfer_bundle() { return &currency_transfer_bundle_; }
    auto* mutable_funding_fee_bundle() { return &funding_fee_bundle_; }
    auto* mutable_nft_token_info() { return &nft_token_info_; }
    int64_t last_transfer_timestamp() const { return last_transfer_timestamp_; }

   public:
    OrderErrorCode error_code_;

    int num_of_events_;
    OrderEvent order_event_[100];

    SubmitOrderResponse submit_response_;
    MultiSubmitResponse multi_submit_response_;
    CancelOrderResponse cancel_response_;
    MultiCancelResponse multi_cancel_response_;
    ConversionBundle conversion_bundle_;
    ProductOrderBundle order_bundle_;
    ProductFillBundle fill_bundle_;
    AccountBalance balance_;
    AccountPosition position_;
    CurrencyTransferBundle currency_transfer_bundle_;
    FundingFeeBundle funding_fee_bundle_;
    NftTokenInfo nft_token_info_;
    int64_t last_transfer_timestamp_;

    int64_t timestamp_;
    int64_t type_bits_;
  };

 public:
  BaseParser() = default;
  virtual ~BaseParser() = default;

  bool is_exwallet() const { return symcache_->is_exwallet(); }
  bool is_spot() const { return symcache_->is_spot(); }
  bool is_margin() const { return symcache_->is_margin(); }
  bool is_futures() const { return symcache_->is_futures(); }
  bool is_swap() const { return symcache_->is_swap(); }
  bool is_linear() const { return symcache_->is_linear(); }
  void SetOcm(OrderContextManager* ocm) { ocm_ = ocm; }

  void SetSymcache(const SymbologyCache* symcache) { symcache_ = symcache; }
  void SetMessageBus(MessageBus* mb) { mb_ = mb; }
  void SetUseCrossMode(bool use_cross_mode) { use_cross_mode_ = use_cross_mode; }
  bool IsValidResponse(const std::shared_ptr<AsioHttpContext>&);
  bool PrepareJsonDocument(JsonDocument*);
  void SetTransferFirstRecord() { transfer_first_record_ = true; }

  virtual const ParseResult& ParseRawMessage(const RawMessage&) { return result_; }
  virtual AccountBalance ParseBalance(const JsonValue&) { return AccountBalance(); }
  virtual AccountPosition ParsePosition(const JsonValue&) { return AccountPosition(); }

  void UpdateTimestamp(int64_t);
  int64_t GetCurrentTimestamp() const;
  auto GetOcm() { return ocm_; }
  void SetIsReadonlyOrderSystem(bool is_readonly) { is_readonly_order_system_ = is_readonly; }

 protected:
  OrderContextManager* ocm_{nullptr};
  const SymbologyCache* symcache_{nullptr};
  ParseResult result_;
  MessageBus* mb_{nullptr};
  const RawMessage* msg_;
  OrderSubTopicId sub_topic_id_;
  bool use_cross_mode_{false};
  int64_t current_ts_{0};
  bool is_readonly_order_system_ = false;
  bool transfer_first_record_ = false;
};

}  // namespace coin2::exchange::base::order
